#
# Processing of Ruby DSL to generate Java persistence descriptor
#
# $Id: pdgen.rb 4 2007-09-27 09:38:28Z dinko.srkoc $

require 'genus/descriptor_type'

# Instance of this class is responsible for extracting information from a DSL file.
# That information is then used to generate the Java persistence descriptor file.
#
# VISE DOKUMENTACIJE IDE OVDJE: kako izgleda DSL, sto se sve upisuje tamo.
class PersistenceDescriptorGenerator
  
  # removal of unneeded methods to reduce the possibility of collision with DSL
  instance_methods.each do |m|
    undef_method(m) unless %w(
      __send__ __id__ send class inspect instance_eval instance_variables extend
    ).include?(m)
  end
  
  Dbms = Struct.new(:block, :type, :username, :password, :host, :port, :database, :server)
  Options = Struct.new(:sql_logging, :ddl_management)
  DataSource = Struct.new(:jndi, :is_jta)

  # If argument is a file name and the file exists, then loads a file
  # for evaluation, otherwise treats the argument as a content to evaluate.
  def initialize(file)
#    args = File.exist?(file) ? [File.open(file).read, file] : file
#    instance_eval(args)
    if File.exist?(file)
      instance_eval(File.open(file).read, file)
    else
      instance_eval(file)
    end
  end
  
  # Object's executable method
  def exec
    # processing data from inside given blocks
    @make_block.call unless @make_block.nil?
    @dbms.block.call unless @dbms.nil? || @dbms.block.nil?
    @specifics[@persistence_impl_type].call if @specifics && @specifics.key?(@persistence_impl_type)
    
    # after collected data we need to choose the implementation (descriptor type)
    extend DescriptorType::descriptor_module(@descriptor_type)
#    case @descriptor_type
#      when :jpa then extend DescriptorType::Jpa
#      when :hibernate_properties then extend DescriptorType::HibernateProperties
#      when :hibernate_xml then extend DescriptorType::HibernateXml
#      else
#        raise "Unknow descriptor type: #@descriptor_type"
#    end
    
    make_descriptor
  end
  
  def method_missing(sym, *args, &blk)
    if sym.to_s =~ /^(username|password|host|port|database|server)_is$/
      @dbms[$1] = args.first
    elsif sym.to_s =~ /^(hibernate|openjpa|toplink)_specific$/
      raise "method #{sym} must have a block assigned" unless block_given?
      @specifics ||= {}
      @specifics[sym] = blk
    else
#      puts "--> method missing: #{sym}"
      super
    end
  end
  
  private
  
  # --- metode opisnika ---
  
  # glavna metoda opisnika
  # params[:descriptor] -> :jpa (default), :hibernate_properties, ...?
  # params[:name]       -> naziv (persistence-unit name)
  #
  # ostali podaci se prikupljaju kroz blk a ovdje se postavljaju defaultne vrijednosti
  def make(params = nil, &blk)
    params ||= {}
    @descriptor_type = params[:descriptor] || :jpa
    @entity_manager_name = params[:name] || 'foo'
    @make_block = blk
    
    @persistence_impl_type = :hibernate_specific
#    @application_runs = :locally ... ako nije navedeno tada ga nema u opisniku
    @options = Options.new(false, nil)
  end
  
  # argument <tt>pi_type</tt> is one of:
  # * :hibernate_specific (default)
  # * :openjpa_specific
  # * :toplink_specific
  def persistence_implementation_is(pi_type)
    @persistence_impl_type = pi_type
  end
  
  # one of: :locally (default), :on_server
  def application_runs(place)
    @application_runs = place
  end
  
  # data_source - optional
  #  args[0] ... jndi name
  #  args[1] ... Hash[:jta] == true/false or :jta/:non-jta
  def data_source_is(*args)
    jndi = args.shift
    jta  = args.shift
    raise "First argument to 'data_source_is' should be JNDI name but its type is #{jndi.class}" unless jndi.instance_of?(String)
    
    is_jta = jta.nil? || (jta.class == Hash && jta[:jta]) || (jta == :jta) # jta - default
    @data_source = DataSource.new(jndi, is_jta)
  end
  
  def dbms_is(dbms, &blk)
    raise "Unknown DBMS type: #{dbms}" unless SUPPORTED_DBMSs.include? dbms
    @dbms = Dbms.new blk, *dbms_defaults(dbms)
  end
  
  def options(*opts)
    @options.sql_logging = opts.include?(:show_sql)
    @options.ddl_management = case
      when opts.include?(:update_database)   then :update
      when opts.include?(:create_database)   then :create
      when opts.include?(:create_drop_database) then :create_drop
      when opts.include?(:drop_database)     then :drop
      when opts.include?(:update_database)   then :update
      when opts.include?(:validate_database) then :validate
      else
        nil
    end
  end
  
  # dodavanje svojstva koje nije predvidjeno dodati na drugi nacin (kao npr. veza prema bazi)
  def add_property(name, value)
    @properties ||= []
    @properties << { :name => name, :value => value }
  end
  
  # --- utility methods ---

  # Reads default values for given DBMS types
  def dbms_defaults(dbms_type)
    data = $dbms_data['jdbc'][dbms_type.to_s]['defaults']
    [ dbms_type, data['username'], data['password'], 'localhost', data['port'], data['database'], data['server'] ]
  end
  
end
